package controllers

import (
	"context"
	"fmt"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	v1alpha12 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers/mocks"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	controllerruntime "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sync"
	"testing"
)

func TestScopeReconciler(t *testing.T) {
	logs.Logger = zap.NewNop()

	ctx := context.Background()
	req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "test", Name: "test"}}
	mClient := mocks.NewClient(t)

	mClient.On("List", mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()

	type fields struct {
		Client          client.Client
		Scheme          *runtime.Scheme
		once            sync.Once
		templateObj     v1alpha12.Scope
		deletedScopeObj v1alpha12.Scope
		initedFlag      bool
	}
	type args struct {
		ctx context.Context
		req controllerruntime.Request
	}

	tt := struct {
		name    string
		fields  fields
		args    args
		wantErr assert.ErrorAssertionFunc
	}{
		name: "Common", fields: struct {
			Client          client.Client
			Scheme          *runtime.Scheme
			once            sync.Once
			templateObj     v1alpha12.Scope
			deletedScopeObj v1alpha12.Scope
			initedFlag      bool
		}{Client: mClient, Scheme: nil, once: sync.Once{}, templateObj: v1alpha12.Scope{}, deletedScopeObj: v1alpha12.Scope{}, initedFlag: true},
		args: struct {
			ctx context.Context
			req controllerruntime.Request
		}{ctx: ctx, req: req}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
			assert.True(t, err == nil)
			return err == nil
		}}

	t.Run("Test AddOrUpdateEventHndl", func(t *testing.T) {
		r := &ScopeReconciler{
			Client:          tt.fields.Client,
			Scheme:          tt.fields.Scheme,
			once:            sync.Once{},
			scopeObj:        tt.fields.templateObj,
			deletedScopeObj: tt.fields.deletedScopeObj,
			initedFlag:      tt.fields.initedFlag,
		}
		tt.wantErr(t, r.AddOrUpdateEventHndl(tt.args.ctx, tt.args.req), fmt.Sprintf("AddOrUpdateEventHndl(%v, %v)", tt.args.ctx, tt.args.req))
	})
}

func TestScopeReconciler_Reconcile(t *testing.T) {
	ctx := context.Background()
	req := controllerruntime.Request{NamespacedName: types.NamespacedName{Namespace: "test", Name: "test"}}
	mClient := mocks.NewClient(t)
	mStatusWriter := mocks.NewSubResourceWriter(t)
	mStatusWriter.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("List", mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Status").Return(mStatusWriter).Maybe()

	type fields struct {
		Client          client.Client
		Scheme          *runtime.Scheme
		once            sync.Once
		scopeObj        v1alpha12.Scope
		deletedScopeObj v1alpha12.Scope
		initedFlag      bool
	}
	type args struct {
		ctx context.Context
		req controllerruntime.Request
	}
	tt := struct {
		name    string
		fields  fields
		args    args
		want    controllerruntime.Result
		wantErr assert.ErrorAssertionFunc
	}{
		name:   "Test reconcile",
		fields: fields{Client: mClient, Scheme: nil, once: sync.Once{}, scopeObj: v1alpha12.Scope{Status: v1alpha12.ScopeStatus{Conditions: []v1alpha12.Condition{{Type: v1alpha12.LastHandledHash}}}}, deletedScopeObj: v1alpha12.Scope{}, initedFlag: true},
		args: args{
			ctx: ctx,
			req: req,
		},
		wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
			assert.True(t, err == nil)
			return err == nil
		},
	}
	t.Run(tt.name, func(t *testing.T) {
		r := &ScopeReconciler{
			Client:          tt.fields.Client,
			Scheme:          tt.fields.Scheme,
			once:            sync.Once{},
			scopeObj:        tt.fields.scopeObj,
			deletedScopeObj: tt.fields.deletedScopeObj,
			initedFlag:      tt.fields.initedFlag,
		}

		_, err := r.Reconcile(tt.args.ctx, tt.args.req)
		if !tt.wantErr(t, err, fmt.Sprintf("Reconcile(%v, %v)", tt.args.ctx, tt.args.req)) {
			return
		}
	})
}

func TestScopeReconciler_manageEvents(t *testing.T) {
	mClient := mocks.NewClient(t)
	type fields struct {
		Client          client.Client
		Scheme          *runtime.Scheme
		once            sync.Once
		templateObj     v1alpha12.Scope
		deletedScopeObj v1alpha12.Scope
		initedFlag      bool
	}
	tt := struct {
		name   string
		fields fields
	}{
		name:   "Test manageEvents",
		fields: fields{Client: mClient, Scheme: nil, once: sync.Once{}, templateObj: v1alpha12.Scope{}, deletedScopeObj: v1alpha12.Scope{}, initedFlag: true},
	}
	t.Run(tt.name, func(t *testing.T) {
		r := &ScopeReconciler{
			Client:          tt.fields.Client,
			Scheme:          tt.fields.Scheme,
			once:            sync.Once{},
			scopeObj:        tt.fields.templateObj,
			deletedScopeObj: tt.fields.deletedScopeObj,
			initedFlag:      tt.fields.initedFlag,
		}
		r.manageEvents()
	})

	scp := v1alpha12.Scope{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Scope",
			APIVersion: "v1alpha",
		}, ObjectMeta: metav1.ObjectMeta{
			Name:       "AAA",
			Namespace:  "test",
			Generation: 0,
		},
		Spec: v1alpha12.ScopeSpec(struct {
			Type  string
			Items []v1alpha12.Item
		}{
			Items: []v1alpha12.Item{{Name: "test"}},
		}),
		Status: v1alpha12.ScopeStatus{
			Phase: "running",
			Conditions: []v1alpha12.Condition{
				{Type: v1alpha12.LastHandledHash, Status: "True", LastTransitionTime: metav1.Now(), Message: "test"},
			},
		},
	}

	scp1 := v1alpha12.Scope{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Scope",
			APIVersion: "v1alpha",
		}, ObjectMeta: metav1.ObjectMeta{
			Name:       "AAA",
			Namespace:  "test",
			Generation: 1,
		},
		Spec: v1alpha12.ScopeSpec(struct {
			Type  string
			Items []v1alpha12.Item
		}{
			Items: []v1alpha12.Item{{Name: "test"}},
		}),
		Status: v1alpha12.ScopeStatus{
			Phase: "running",
			Conditions: []v1alpha12.Condition{
				{Type: v1alpha12.LastHandledHash, Status: "True", LastTransitionTime: metav1.Now(), Message: "test"},
			},
		},
	}

	r := ScopeReconciler{}
	p := r.manageEvents()

	// Create test
	assert.True(t, p.Create(event.CreateEvent{Object: &scp}))

	// Update test
	assert.True(t, p.Update(event.UpdateEvent{ObjectNew: &scp, ObjectOld: &scp1}))
	assert.False(t, p.Update(event.UpdateEvent{ObjectNew: &scp, ObjectOld: &scp}))

	//Delete test
	assert.True(t, p.Delete(event.DeleteEvent{Object: &scp}))

}

func TestScopeReconciler_HealthCheck(t *testing.T) {
	t.Run("test", func(t *testing.T) {
		r := &ScopeReconciler{
			initedFlag: false,
		}
		assert.Equalf(t, false, r.HealthCheck(), "HealthCheck()")
	})
}

func TestScopeReconciler_updateStatus(t *testing.T) {
	logs.Logger = zap.NewNop()

	scope := v1alpha12.Scope{Spec: v1alpha12.ScopeSpec{Type: "pod"}, Status: v1alpha12.ScopeStatus{
		Conditions: []v1alpha12.Condition{
			{Type: v1alpha12.LastHandledHash,
				Message: "{\"type\":\"pod\",\"items\":null}"},
		},
	}}
	var isAudit bool
	isAudit = true

	mClient := mocks.NewClient(t)
	mStatusWriter := mocks.NewSubResourceWriter(t)
	mStatusWriter.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("List", mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
	mClient.On("Status").Return(mStatusWriter).Maybe()

	sr := ScopeReconciler{
		Client: mClient,
		Scheme: nil,
		once:   sync.Once{},
		scopeObj: v1alpha12.Scope{
			Spec: v1alpha12.ScopeSpec{Type: "pod"},
			Status: v1alpha12.ScopeStatus{
				Conditions: []v1alpha12.Condition{
					{Type: v1alpha12.LastHandledHash,
						Message: "{\"type\":\"pod\",\"items\":null}"},
				},
			}},
		deletedScopeObj: v1alpha12.Scope{},
		initedFlag:      false,
	}

	_, err := sr.updateStatus(context.Background(), scope, isAudit)
	if err != nil {
		t.Fatal("error", err)
	}
}
